package zzu.xjc.http.util;

import java.lang.reflect.*;
import java.util.*;

public class JSON {
    private Object obj;
    private JSON(Object obj){
        this.obj = obj;
    }
    public static Object parseToObject(String json,Class type){
        Object obj = parse(json);
        Object target = null;
        try {
            target = Mapper.mapValue(obj,type);
        } catch (JSONParseException | InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return target;
    }
    public static JSON parseToJSON(String jsonStr){
        return new JSON(parse(jsonStr));
    }
    private static Object parse(String json){
        Object result = null;
        try {
            result = new Parser(json.replaceAll(" ","").toCharArray(), 0 ).parseValue();

        } catch (JSONParseException e) {
            e.printStackTrace();
        }
        return result;
    }

    private static class Parser{
        private final char[] charArray;
        private int curr;
        Parser(char[] charArray,int curr){
            this.charArray = charArray;
            this.curr = curr;
        }

        Object parseValue() throws JSONParseException {
            char c = charArray[curr];
            switch (c){
                case '{': return parseObject();
                case '[': return parseArray();
                case '"': return parseString();
                case 't': return parseTrue();
                case 'f': return parseFalse();
                case 'n': return parseNull();
                case '\\': return parseEscapeSymbol();
                default:{
                    if ( c >= '0' && c <= '9'){
                       return parseNumber();
                    }
                }
            }
            return null;
        }

        Object parseObject() throws JSONParseException {
            Map<String,Object> map = new HashMap<>();
            curr++;
            while (true){
                String key = parseString();
                // now curr point to ':'
                if (charArray[curr] != ':'){
                    throw new JSONParseException("expect separator ':', but " + charArray[curr]);
                }
                curr++;
                // now curr point to the char after ':'
                Object value = parseValue();
                map.put(key,value);
                if (charArray[curr] == ','){
                    curr ++;
                }else if (charArray[curr] == '}'){
                    curr ++;
                    break;
                }else{
                    throw new JSONParseException("unknown separator char :" + charArray[curr]);
                }
            }
            return map;
        }
        Object parseArray() throws JSONParseException {
            List<Object> list = new LinkedList<>();
            curr++;
            while (charArray[curr] != ']'){
                curr++;
                list.add(parseValue());
            }
            curr++;
            return list;
        }
        String parseString() {
            int begin = ++curr;
            while (charArray[curr] != '"'){
                curr++;
            }
            String res = new String(charArray, begin, curr - begin);
            curr++; // make the curr point to the char after the '"'
            return res;
        }
        Object parseNumber() {
            StringBuilder str = new StringBuilder();
            while ((charArray[curr] >= '0' && charArray[curr] <= '9') || charArray[curr]=='.' ){
                str.append(charArray[curr]);
                curr++;
            }
            String s = str.toString();
            if (s.contains(".")) {
            // float point number
                return Double.parseDouble(s);
            }
            // integer
            return Long.parseLong(s);
        }

        private static final char[] trueBytes = {'t','r','u','e'};

        Object parseTrue() throws JSONParseException {
            for (int i = 0; i < trueBytes.length; i++) {
                if (charArray[curr] != trueBytes[i]){
                    throw new JSONParseException("Match \"true\" failed , unknown char: " + charArray[curr]);
                }
                curr++;
            }
            return true;
        }
        private static final char[] falseBytes = {'f','a','l','s','e'};
        Object parseFalse() throws JSONParseException {
            for (int i = 0; i < falseBytes.length; i++) {
                if (charArray[curr] != falseBytes[i]){
                    throw new JSONParseException("Match \"false\" failed , unknown char: " + charArray[curr]);
                }
                curr++;
            }
            return false;
        }
        private static final char[] nullBytes = {'n','u','l','l'};
        Object parseNull() throws JSONParseException {
            for (int i = 0; i < nullBytes.length; i++) {
                if (charArray[curr] != nullBytes[i]){
                    throw new JSONParseException("Match \"null\" failed , unknown char: " + charArray[curr]);
                }
                curr++;
            }
            return null;
        }
        Object parseEscapeSymbol() {
            return null;
        }
    }
    private static class Mapper{
        private static final HashSet<Class<?>> intTypes = new HashSet<>();
        private static final HashSet<Class<?>> floatTypes = new HashSet<>();
        private static final HashSet<Class<?>> arrayTypes = new HashSet<>();
        private static final HashSet<Class<?>> boolTypes = new HashSet<>();
        static {
            intTypes.addAll(List.of(Integer.class,Long.class,Short.class,Byte.class));
            intTypes.addAll(List.of(int.class,long.class,short.class,byte.class));
            floatTypes.addAll(List.of(Float.class,float.class,Double.class,double.class));
            arrayTypes.addAll(List.of(List.class,Object[].class,LinkedList.class,ArrayList.class));
            boolTypes.addAll(List.of(boolean.class,Boolean.class));
        }

        static Object mapValue(Object json,Class<?> type) throws JSONParseException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
            if (json == null) {
                return null;
            } else if (intTypes.contains(type)&&intTypes.contains(json.getClass())){
                return mapInteger(json,type);
            }else if (floatTypes.contains(type)&&floatTypes.contains(json.getClass())){
                return mapFloat(json, type);
            }else if (boolTypes.contains(type)&&boolTypes.contains(json.getClass())){
                return json;
            }else if (arrayTypes.contains(type)&&arrayTypes.contains(json.getClass())){
                return mapList(json,type);
            }else if (String.class.equals(type)&&String.class.equals(json.getClass())){
                return json;
            } else{
                Logger.info("mapValue() " + type.getCanonicalName() + " " + json);
                return mapObject(json,type);
            }
        }

        private static Object mapInteger(Object json, Class<?> type) {
            Long num = (Long) json;
            if (type.equals(Integer.class) || type.equals(int.class)){
                return num.intValue();
            }
            if (type.equals(Short.class)||type.equals(short.class)){
                return num.shortValue();
            }
            if (type.equals(Byte.class)||type.equals(byte.class)){
                return num.byteValue();
            }
            return num;
        }
        private static Object mapFloat(Object json,Class<?> type){
            Double num = (Double) json;
            if (type.equals(Float.class) || type.equals(float.class)){
                return num.floatValue();
            }
            return num;
        }

        private static Object mapObject(Object json, Class<?> type) throws JSONParseException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
            if (! (json instanceof Map)){
                throw new JSONParseException("unexpected json type , " + json.getClass().getCanonicalName());
            }
            Constructor<?> constructor = type.getConstructor();
            constructor.setAccessible(true);
            Object target = constructor.newInstance();
            Map<String,Object> map = (Map<String, Object>) json;
            Field[] fields = type.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true);
                String key = fields[i].getName();
                Object value = map.get(key);
                Object o = mapValue(value, fields[i].getType());
                Logger.info(o.toString());
                fields[i].set(target,o);
            }
            return target;
        }
        static Object mapList(Object json, Class<?> type ) throws JSONParseException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
            if (Object[].class.equals(type)){
                Class<?> elemType = Object[].class.getComponentType();
                if(json instanceof List<?>){
                    List<Object> objects = (List<Object>) json;
                    List<Object> list = new ArrayList<>();
                    for (Object obj : objects) {
                        list.add(mapValue(obj,elemType));
                    }
                    return list.toArray();
                }
            }else if (List.class.equals(type)){
                return json;
            }
            return null;
        }
    }

    public JSON get(String key){
        if (obj instanceof Map){
            Map<String,Object> map = (Map<String, Object>) obj;
            if (map.containsKey(key)){
                return new JSON(map.get(key));
            }
        }
        return null;
    }
    public JSON at(int index){
        if (obj instanceof List){
            List<Object> list = (List<Object>) obj;
            if (index < list.size()){
                return new JSON(list.get(index));
            }
        }
        return null;
    }
    public Object value(){
        return obj;
    }

    public static String stringify(Object obj){
        try {
            return new Stringifier().stringifyValue(obj).toString();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static class Stringifier{
        private static final HashSet<Class<?>> valueTypes = new HashSet<>();
        private static final HashSet<Class<?>> arrayTypes = new HashSet<>();
        static {
            valueTypes.addAll(List.of(Integer.class,Long.class,Short.class,Byte.class));
            valueTypes.addAll(List.of(int.class,long.class,short.class,byte.class));
            valueTypes.addAll(List.of(Float.class,float.class,Double.class,double.class));
            valueTypes.addAll(List.of(boolean.class,Boolean.class));
            arrayTypes.addAll(List.of(List.class,Object[].class,LinkedList.class,ArrayList.class));
        }
        private final StringBuilder result = new StringBuilder();
        Stringifier stringifyValue(Object obj) throws IllegalAccessException {
            if (obj == null){
                result.append("null");
            }else if (String.class.equals(obj.getClass())){
                result.append("\"").append(obj).append("\"");
            }else if (valueTypes.contains(obj.getClass())){
                result.append(String.valueOf(obj));
            }else if (arrayTypes.contains(obj.getClass())){
                stringifyList(obj);
            }else{
                Logger.info("stringifyValue() " + obj.getClass().getCanonicalName());
                if (obj instanceof JSON){
                    stringifyObject(((JSON)obj).obj);
                }else{
                    stringifyObject(obj);
                }
            }
            return this;
        }
        void stringifyList(Object obj) throws IllegalAccessException {
            result.append("[");
            if (obj instanceof Object[]){
                Object[] objects = (Object[]) obj;
                for (Object o: objects){
                    stringifyValue(o);
                    result.append(",");
                }
            } else if (obj instanceof List){
                List<Object> list = (List<Object>) obj;
                for (int i = 0 ; i < list.size() ; i++) {
                    stringifyValue(list.get(i));
                    result.append(",");
                }
            }else{
                Logger.info("stringifyList() " + obj.getClass().getCanonicalName());
            }
            if (result.charAt(result.length() - 1) == ','){
                result.deleteCharAt(result.length() - 1);
            }
            result.append("]");
        }
        void stringifyObject(Object obj) throws IllegalAccessException {
            result.append("{");
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Object o = field.get(obj);
                result.append("\"").append(field.getName()).append("\":");
                stringifyValue(o);
                result.append(",");
            }
            result.deleteCharAt(result.lastIndexOf(",")).append("}");
        }
        @Override
        public String toString() {
            return result.toString();
        }
    }

    static class JSONParseException extends Exception{
        public JSONParseException(String message) {
            super(message);
        }
    }
}
